#include "imageview.h"
#include "chip8_def.hpp"
#include <algorithm>
#include <QString>


/*
只绘制图像中需要显示的部分以提高大尺寸图像的显示效率。
*/
class LazyImageItem : public QGraphicsItem {
public:
	LazyImageItem() {

	}

	LazyImageItem(const QImage& image, QGraphicsItem* parent = nullptr) :
		QGraphicsItem(parent) {
		setImage(image);
	}

	void setImage(const QImage& image) {
		prepareGeometryChange();
		this->mImage = image;
	}

	QImage& getImage() {
		return mImage;
	}

	QRectF boundingRect() const override {
		if (mImage.isNull())
			return QRectF();
		return QRectF(QPoint(0, 0), mImage.size() / mImage.devicePixelRatio());
	}

	int width() const {
		return mImage.width();
	}

	int height() const {
		return mImage.height();
	}

	/*
	@brief 指定需要显示的区域，更新显示
	*/
	void updateWindow(QRectF window) {
		this->window = window;
		update();
	}

protected:
	void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override {
		auto ratio = mImage.devicePixelRatioF();
		QRectF imageBounding(0, 0, mImage.width(), mImage.height());
		auto itemBounding = boundingRect();
		auto targetRect = window & itemBounding;
		auto sourceRect = QRectF(targetRect.x() * ratio, targetRect.y() * ratio, targetRect.width() * ratio, targetRect.height() * ratio)  & imageBounding;
		painter->drawImage(targetRect, mImage, sourceRect);
		// printf("image: %d, %d;\n", mImage.width(), mImage.height());
		// printf("bounding: %lf, %lf, %lf, %lf\n", itemBounding.x(), itemBounding.y(), itemBounding.width(), itemBounding.height());
		// printf("source: %lf, %lf, %lf, %lf\n", sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height());
		// printf("target: %lf, %lf, %lf, %lf\n", targetRect.x(), targetRect.y(), targetRect.width(), targetRect.height());		
	}

private:
	bool autoMaximizeContrast = false;
	QImage mImage;
	QRectF window;
};

class ImageView::Private {
public:
	LazyImageItem* imageItem = nullptr;
	ImageView* d;
	double scale = 1.0;
	QPointF dragPointOnScene;
	QPointF latestPointOnScene;

	int viewWidth() {
		return d->viewport()->rect().width();
	}

	int viewHeight() {
		return d->viewport()->rect().height();
	}

	/*
	@brief 重新计算图片范围并绘制图片
	*/
	void updateImageWindow() {
		imageItem->updateWindow(d->sceneWindow());
	}

	Private(ImageView* imageView):
		d{ imageView },
		imageItem{ new LazyImageItem() } {}
};

ImageView::ImageView(QWidget* parent) : 
	QGraphicsView(parent){
	d = new Private(this);
	setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
	setScene(new QGraphicsScene());
	scene()->addItem(d->imageItem);
	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	connect(this, &ImageView::windowChanged, [this]() {d->updateImageWindow(); });
}

ImageView::~ImageView() {
	SDELETE(d);
}

void ImageView::setImage(const QImage & img, bool centerOnImage)
{	
	int width = img.width(), height = img.height();
	d->imageItem->setImage(img);
	setSceneRect(0, 0, width, height);
	if(centerOnImage) centerOnImgCenter();
	emit imageChanged();
}

QRectF ImageView::sceneWindow() const {
	return mapToScene(viewport()->rect()).boundingRect();
}

void ImageView::centerOnImgCenter() {
	centerOn(d->imageItem->width() / 2, d->imageItem->height() / 2);
}

void ImageView::resetScale() {
	resetTransform();
	centerOnImgCenter();
	setScale(1);
}

void ImageView::setScale(double scale) {
	scale = fmin(fmax(scaleLmtLow, scale), scaleLmtUp);
	setTransform(QTransform::fromScale(scale, scale));
	d->scale = scale;
}

void ImageView::move(QPointF viewDelta) {
	QPoint newCenter(d->viewWidth() / 2 + viewDelta.x(), d->viewHeight() / 2 + viewDelta.y());
	centerOn(mapToScene(newCenter));
}

void ImageView::fitViewport() {
	QSize&& size = d->imageItem->getImage().size();
	fitViewport(0, 0, size.width(), size.height());
}

void ImageView::fitViewport(double x, double y, double w, double h) {
	double scaleH = (double)d->viewWidth() / w;
	double scaleV = (double)d->viewHeight() / h;
	setScale(std::min(scaleH, scaleV));
	centerOn(x + w / 2, y + h / 2);
}

bool ImageView::hasImage() const {
	return !d->imageItem->getImage().isNull();
}

void ImageView::removeImage() {
	d->imageItem->setImage(QImage());
}

void ImageView::centerOn(const QPointF& pos) {
	QGraphicsView::centerOn(pos);
	emit windowChanged();
}

void ImageView::centerOn(qreal ax, qreal ay) {
	QGraphicsView::centerOn(ax, ay);
	emit windowChanged();
}

void ImageView::centerOn(const QGraphicsItem* item) {
	QGraphicsView::centerOn(item);
	emit windowChanged();
}

QPointF ImageView::getCursorPosition() const {
	return d->latestPointOnScene;
}

void ImageView::setTransform(const QTransform& matrix, bool combined) {
	QGraphicsView::setTransform(matrix, combined);
	emit windowChanged();
}

void ImageView::wheelEvent(QWheelEvent* wheelEvent) {
	QPoint numDegrees = wheelEvent->angleDelta();
	if (numDegrees.isNull()) return;
	double value = numDegrees.y() / 120.0 / 10;
	setScale(d->scale*(1 + value));
	QPoint pos = wheelEvent->pos();
	if (!d->latestPointOnScene.isNull()) {
		QPoint pos1 = mapFromScene(d->latestPointOnScene);
		move(pos1 - pos);
	}
}

void ImageView::mouseMoveEvent(QMouseEvent* event) {
	QGraphicsView::mouseMoveEvent(event);
	QPoint pos0 = event->pos();
	d->latestPointOnScene = mapToScene(pos0);
	if (!d->dragPointOnScene.isNull()) {
		QPoint pos1 = mapFromScene(d->dragPointOnScene);
		QPointF dView = pos1 - pos0;
		move(dView);
	}
}

void ImageView::mousePressEvent(QMouseEvent* event) {
	QGraphicsView::mousePressEvent(event);
	QPoint pos = event->pos();
	d->dragPointOnScene = mapToScene(pos);
}

void ImageView::mouseReleaseEvent(QMouseEvent* event) {
	QGraphicsView::mouseReleaseEvent(event);
	d->dragPointOnScene = QPoint();
}

void ImageView::leaveEvent(QEvent * event)
{
	QGraphicsView::leaveEvent(event);
	d->latestPointOnScene = QPointF(-1, -1);
}

void ImageView::scrollContentsBy(int dx, int dy) {
	QGraphicsView::scrollContentsBy(dx, dy);
	emit windowChanged();
}

void ImageView::contextMenuEvent(QContextMenuEvent* event) {
	QMenu context;
	context.exec(QCursor::pos());
}

void ImageView::resizeEvent(QResizeEvent* event) {
	emit windowChanged();
}